home *** CD-ROM | disk | FTP | other *** search
- /*
- File: ThreadUtil.c
-
- Contains: Routines for Thread Manager Utilities
-
- Written by: Brad Post, Eric Anderson and Bill Knott
-
- Copyright: © 1993 by Apple Computer, Inc., all rights reserved.
-
- Change History (most recent first):
-
- 5/8/93 bsp Added the initialization of busy to the CreateLinkedListSemaphore.
-
- 4/19/93 bsp Added the code to handle the busy boolean in GetLinkedListSemaphore and
- ReleaseLinkedListSemaphore.
-
- 3/3/93 bsp Added code to handle the lastInList element of the LLSemaphores.
- 2/19/93 bsp Fixed a bug in DeleteArraySemaphore, I was off by one Ptr, so was getting
- a MemErr == -111 (WhichZone failed).
- 2/16/93 bsp Added the pragma segment. Fixed a bug in ReleaseLL and ReleaseArray, so it makes the
- semaphore really available. Added funtion InitializeThreadUtilities.
- 2/9/93 bsp Cleaned up the interface.
- 2/8/93 bsp Changed functions to use anArraySemaphorePtr. Modified CreateArraySemaphore to use
- anArraySemaphorePtr * and create the semaphore. Also do a check if maxInQueue is
- a valid number > 0 else return a threadProtocolErr. Changed DeleteArraySemaphore to
- delete the array and then the semaphore. Made all functions of type pascal. Added
- checks for aSemaphore == NULL and if so return a threadProtocolErr where appropriate.
- 2/5/93 bsp Added CreateLinkedListSemaphore, DeleteLinkedListSemaphore, DeleteSimpleSemaphore and
- CreateSimpleSemaphore. Also made DisposePtr w/in DeleteArraySemaphore a critical area.
- Changed functions to use LinkedListSemaphorePtr, aSimpleSemaphorePtr, and anArraySemaphorePtr.
- 2/4/93 bsp Added TestAndSetByte to get rid of trap calls to Begin & End, changed it to
- 'seq d0' to reflect changes made in header file. Removed the ThreadBegin &
- ThreadEnd from releaseSimpleSemaphore. Enclosed the Critical area in
- releaseLinkedListSemaphore & releaseArraySemaphore.
- 2/2/93 bsp Added functions to handle semaphore models.
- 1/31/93 ewa New Today.
-
- To Do:
- Figure out what we do if the app quits out from under us
-
- */
-
- #include "ThreadUtil.h"
-
- #pragma segment ThreadUtilities
-
- /*
- * InitializeThreadUtilities()
- *
- * INPUT: void
- * RETURN: void
- * WHEN CAN BE CALLED: From cooperative thread only
- *
- * This function does nothing but force LoadSeg to load in this segment.
- */
- pascal void InitializeThreadUtilities()
- {
-
- }
-
- #pragma parameter __D0 TestAndSetByte(__A0)
- short TestAndSetByte(Byte *pByte)
- = {0x7000, 0x08D0, 0x0000, 0x57C0};
- /* moveq #0, d0
- * bset #0, (a0) Test bit 0, if == 0, set to 1
- * seq d0 If set to 1, make all 1's else all 0's
- */
-
- /* structs & defines */
- #define kNumAttempts 3 // number of times to try and install the time task
-
- #define HELL_NOT_FROZEN_OVER 1 // used for while loops
-
- /* Used by SimpleSemaphore Model */
- #define kIsAvailable false // value of the semaphore, since we use TestAndSetByte
- #define kNotAvailable true // valud of the semaphore, since we use TestAndSetByte
-
- /* Used by Array Semaphore Model */
- #define kNobodyWaiting -1
-
- struct TMInfo {
- TMTask theTMTask;
- ThreadTaskRef theAppTask;
- ThreadID theThread;
- unsigned long theDelay;
- short numAttempts;
- };
- typedef struct TMInfo TMInfo;
- typedef TMInfo *TMInfoPtr;
-
-
- /* internal routine defines */
- pascal void SleepTerminator (ThreadID threadDead, TMInfoPtr theTMTaskPtr);
- pascal TMInfoPtr GetTMInfo (void) = {0x2E89}; /* move.l a1,(sp) */
- pascal void WakeTMTask (void);
-
- /* routine implementations */
-
- /*
- * DelayThread
- *
- * INPUT: unsigned long
- * RETURN: void
- * WHEN CAN BE CALLED: From cooperative or preemptive thread
- *
- * This function sets up a time manager task to wake up the sleeping thread after a certain number of ticks.
- * It puts the thread to sleep and says to schedule anyone next.
- *
- * SIDE EFFECTS: Destroys any Thread Terminator task that existed for the thread.
- */
- pascal void
- DelayThread (unsigned long millisecDelay) {
- TMInfo myTask;
- OSErr err;
-
- /* fill in the record */
- myTask.theTMTask.tmAddr = (TimerProcPtr)(WakeTMTask);
- myTask.theTMTask.tmWakeUp = 0;
- myTask.theTMTask.tmReserved = 0;
- myTask.theDelay = millisecDelay; /* in case we need to poll until the thread can sleep */
- myTask.numAttempts = kNumAttempts; /* re-prime this many times before giving up */
-
- /* including the app task ref */
- err = GetThreadCurrentTaskRef (&(myTask.theAppTask));
- if (err)
- return;
- /* and the current thread ID */
- err = GetCurrentThread (&(myTask.theThread));
- if (err)
- return;
- /* Begin Critical section so we do not get bothered by busy-bodies after we have primed */
- ThreadBeginCritical();
- err = SetThreadTerminator (kCurrentThreadID, (ThreadTerminationProcPtr)(SleepTerminator), &myTask);
-
- /* install, prime, and put us to sleep */
- InsTime ((QElemPtr)&myTask);
- PrimeTime ((QElemPtr)&myTask, millisecDelay);
-
- /* Hopefully our timer does not fire before we can sleep... */
- /* ...but if it does, the TM task will re-prime and try again */
- SetThreadStateEndCritical (kCurrentThreadID, kStoppedThreadState, kNoThreadID);
-
- /* We are now awake, so lets blow this joint */
- err = SetThreadTerminator (kCurrentThreadID, (ThreadTerminationProcPtr)(nil), nil);
- RmvTime ((QElemPtr)&myTask);
- }
-
- /*
- * SleepTerminator
- *
- * INPUT: ThreadID, TMInfoPtr
- * RETURN: void
- *
- * This is the terminator task, it is called if the thread we put to sleep never wakes up before the thread
- * is terminated. This removed the time manager task from the queue.
- */
- pascal void
- SleepTerminator (ThreadID threadDead, TMInfoPtr theTMTaskPtr) {
- #pragma unused (threadDead)
- RmvTime ((QElemPtr)theTMTaskPtr);
- }
-
- /*
- * WakeTMTask
- *
- * INPUT: void
- * RETURN: void
- *
- * This function is called by the time Manager to wake up the thread that we put to sleep with DelayThread.
- */
- pascal void
- WakeTMTask (void) {
- ThreadTaskRef theAppTaskRef;
- ThreadID theThreadID;
- ThreadState theThreadState;
- TMInfoPtr theTMTaskPtr;
-
- /* get the record from register A1 before anything else */
- theTMTaskPtr = GetTMInfo();
-
- /* Get our data */
- theAppTaskRef = theTMTaskPtr->theAppTask;
- theThreadID = theTMTaskPtr->theThread;
-
- /* See if the thread is stopped yet */
- GetThreadStateGivenTaskRef (theAppTaskRef, theThreadID, &theThreadState);
- /* if it is awake, re-prime and try again */
- if (theThreadState != kStoppedThreadState && theTMTaskPtr->numAttempts > 0)
- {
- (theTMTaskPtr->numAttempts)--;
- PrimeTime ((QElemPtr)theTMTaskPtr, theTMTaskPtr->theDelay); /* re-prime to try again! */
- }
- /* if it is sleeping, mark it for wake up! */
- else
- SetThreadReadyGivenTaskRef (theAppTaskRef, theThreadID);
- }
-
-
- /*
- * Below are all the functions necessary for handling the three types of semaphore models.
- * The simple SyncWait Model (SimpleSemaphore), Queue up with infinte number of elements in the queue
- * (LinkedListSemaphore), and Fixed number of people in queue (ArraySemaphore). For more information
- * on semaphores, read your favorite Operating Systems book.
- */
-
- /*
- * CreateSimpleSemaphore
- *
- * INPUT: SimpleSemaphorePtr *
- * RETURN: OSErr
- * WHEN CAN BE CALLED: Only from a cooperative thread
- *
- * This function initializes the semaphore to isAvailable. Returns the respective MemError().
- */
- pascal OSErr CreateSimpleSemaphore( SimpleSemaphorePtr *aSemaphore )
- {
- ThreadBeginCritical();
-
- *aSemaphore = (SimpleSemaphore *) NewPtr(sizeof(SimpleSemaphore));
-
- if(*aSemaphore == NULL)
- {
- ThreadEndCritical();
- return MemError();
- }
-
- **aSemaphore = kIsAvailable;
-
- ThreadEndCritical();
-
- return noErr;
- }
-
- /*
- * GetSimpleSemaphore
- *
- * INPUT: SimpleSemaphorePtr
- * RETURN: OSErr
- * WHEN CAN BE CALLED: From cooperative and preemptive threads
- *
- * This function just waits until the semaphore is available, when it is, we make it unavailable and
- * return. Otherwise just keep Yielding to whomever else is around.
- */
- pascal OSErr GetSimpleSemaphore(SimpleSemaphorePtr aSemaphore)
- {
- if(aSemaphore == NULL)
- return semaphoreButtHeadErr;
-
- while( HELL_NOT_FROZEN_OVER )
- {
- if( TestAndSetByte(aSemaphore) ) // if semaphore == isAvailable, set it to notAvailable
- {
- break;
- } else {
- YieldToAnyThread();
- }
- }
-
- return noErr;
- }
-
- /*
- * ReleaseSimpleSemaphore
- *
- * INPUT: SimpleSemaphorePtr
- * RETURN: OSErr
- * WHEN CAN BE CALLED: From cooperative and preemptive threads
- *
- * This function just sets the semaphore to available. Returns an error if something wrong, else noErr.
- */
-
- pascal OSErr ReleaseSimpleSemaphore(SimpleSemaphorePtr aSemaphore)
- {
- if(aSemaphore == NULL)
- return semaphoreButtHeadErr;
-
- *aSemaphore = kIsAvailable;
-
- return noErr;
- }
-
- /*
- * DeleteSimpleSemaphore
- *
- * INPUT: SimpleSemaphorePtr
- * RETURN: OSErr
- * WHEN CAN BE CALLED: Only from a cooperative thread
- *
- * This function disposes the semaphore, returning the respective MemError(). If the semaphore was NULL,
- * return a semaphoreButtHeadErr.
- */
- pascal OSErr DeleteSimpleSemaphore( SimpleSemaphorePtr aSemaphore )
- {
- ThreadBeginCritical();
-
- if(aSemaphore != NULL)
- {
- DisposePtr( (Ptr) aSemaphore );
- ThreadEndCritical();
- return MemError();
- }
-
- ThreadEndCritical();
- return semaphoreButtHeadErr;
- }
-
-
- /*
- * CreateLinkedListSemaphore
- *
- * INPUTS: LinkedListSemaphorePtr *
- * RETURNS: OSErr
- * WHEN CAN BE CALLED: Only from a cooperative thread
- *
- * This function initializes the LinkedListSemaphore, returning the respective MemError().
- */
-
- pascal OSErr CreateLinkedListSemaphore( LinkedListSemaphorePtr *aSemaphore )
- {
- ThreadBeginCritical();
-
- *aSemaphore = (LinkedListSemaphore *) NewPtr (sizeof(LinkedListSemaphore));
-
- if(*aSemaphore == NULL)
- {
- ThreadEndCritical();
- return MemError();
- }
-
- (*aSemaphore)->waitingList = NULL;
- (*aSemaphore)->lastInList = (*aSemaphore)->waitingList;
- (*aSemaphore)->theHolder = kNoThreadID;
- (*aSemaphore)->available = kIsAvailable;
- (*aSemaphore)->busy = false;
-
- ThreadEndCritical();
-
- return noErr;
- }
-
- /*
- * GetLinkedListSemaphore
- *
- * INPUTS: LinkedListElementPtr, LinkedListSemaphorePtr
- * RETURNS: OSErr
- * WHEN CAN BE CALLED: From cooperative and preemptive threads
- *
- * The function checks to see if the semaphore is available. If it is, then we grab it and return.
- * If the sempahore is not available, then we place ourself in the waiting queue and put ourselves
- * into the stopped mode (sleeping). Return an error if somethings wrong, else noErr.
- */
- pascal OSErr GetLinkedListSemaphore(LinkedListElementPtr whichOne, LinkedListSemaphorePtr aSemaphore )
- {
- if(aSemaphore == NULL)
- return semaphoreButtHeadErr;
-
- if(GetCurrentThread(&(whichOne->whoAmI)) != noErr)
- {
- DebugStr("\pFailed getting current Thread ID withing GetLinkedListSemaphore -- very bad");
- }
-
- whichOne->next = NULL;
-
- while( !TestAndSetByte(&(aSemaphore->busy)) ) ; // Spin & wait until semaphore isn't busy
-
- if( TestAndSetByte(&(aSemaphore->available)) ) // aSemaphore->available == true
- {
- aSemaphore->theHolder = whichOne->whoAmI;
- aSemaphore->busy = false; // we are safe to say we aren't busy, because can't be interrupted
- }
- else if(whichOne->whoAmI != aSemaphore->theHolder)
- {
- ThreadBeginCritical();
-
- aSemaphore->busy = false; // we are safe to say we aren't busy, because can't be interrupted
- if(aSemaphore->waitingList == NULL)
- {
- aSemaphore->waitingList = whichOne;
- aSemaphore->lastInList = aSemaphore->waitingList;
- }
- else
- {
- aSemaphore->lastInList->next = whichOne;
- aSemaphore->lastInList = whichOne;
- }
-
- SetThreadStateEndCritical(kCurrentThreadID, kStoppedThreadState, kNoThreadID);
- }
-
- return noErr;
- }
-
- /*
- * ReleaseLinkedListSemaphore
- *
- * INPUTS: LinkedListSemaphorePtr
- * RETURNS: OSErr
- * WHEN CAN BE CALLED: From cooperative and preemptive threads
- *
- * This function releases the semaphore only if there is someone waiting on it. If there
- * is then we release the semaphore, make the new holder the one who was waiting. Pop him
- * off the waiting queue, make him ready and set our state to READY, and ask that the new
- * owner of the semaphore be set to run. Return an error if something is wrong else noErr.
- */
- pascal OSErr ReleaseLinkedListSemaphore( LinkedListSemaphorePtr aSemaphore )
- {
- if(aSemaphore == NULL)
- return semaphoreButtHeadErr;
-
- while( !TestAndSetByte(&(aSemaphore->busy)) ) ; // Spin & wait until semaphore isn't busy
-
- if(aSemaphore->waitingList != NULL)
- {
- ThreadBeginCritical();
-
- aSemaphore->busy = false; // we are safe to say we aren't busy, because can't be interrupted
-
- aSemaphore->theHolder = aSemaphore->waitingList->whoAmI;
- aSemaphore->waitingList = aSemaphore->waitingList->next;
-
- if(aSemaphore->waitingList == NULL)
- aSemaphore->lastInList = NULL;
-
- SetThreadState(aSemaphore->theHolder, kReadyThreadState, kNoThreadID);
-
- SetThreadStateEndCritical(kCurrentThreadID, kReadyThreadState, aSemaphore->theHolder);
- } else {
- aSemaphore->available = kIsAvailable;
- aSemaphore->busy = false; // we are safe to say we aren't busy, because can't be interrupted
- }
-
- return noErr;
- }
-
- /*
- * DeleteLinkedListSemaphore
- *
- * INPUTS: LinkedListSemaphorePtr
- * RETURNS: OSErr
- * WHEN CAN BE CALLED: Only from a cooperative thread
- *
- * This function deletes the LinkedListSemaphore, returning a valid one. If the semaphore was == NULL
- * then return a semaphoreButtHeadErr. If there are threads still waiting on the semaphore return a
- * semaphoreWaitErr.
- */
- pascal OSErr DeleteLinkedListSemaphore( LinkedListSemaphorePtr aSemaphore )
- {
- ThreadBeginCritical();
-
- if(aSemaphore != NULL)
- {
- if(aSemaphore->waitingList != NULL)
- {
- ThreadEndCritical();
- return semaphoreWaitErr;
- }
- DisposePtr( (Ptr) aSemaphore);
- ThreadEndCritical();
- return MemError();
- }
-
- ThreadEndCritical();
- return semaphoreButtHeadErr;
- }
-
-
-
- /*
- * CreateArraySemaphore
- *
- * INPUT: long, ArraySemaphorePtr
- * RETURN: OSErr
- * WHEN CAN BE CALLED: Only from a cooperative thread
- *
- * This function allocates space for the array that holds the list of Threads waiting for the
- * semaphore. It also initializes all the data needed to use an Array Semaphore. This returns
- * any error it may encounter.
- */
- pascal OSErr CreateArraySemaphore(long maxInQueue, ArraySemaphorePtr *aSemaphore)
- {
- long index;
-
- /* If maxInQueue is < 0, definitely an error. If == 0, why are you wasting the space in
- * using this type a semaphore, the SimpleSemaphore model is better. */
- if(maxInQueue <= 0)
- return semaphoreButtHeadErr;
-
- ThreadBeginCritical();
-
- *aSemaphore = (ArraySemaphore *) NewPtr (sizeof(ArraySemaphore));
-
- if(*aSemaphore == NULL)
- {
- ThreadEndCritical();
- return MemError();
- }
-
- (*aSemaphore)->available = kIsAvailable;
- (*aSemaphore)->theHolder = kNoThreadID; // default value == noHolder
- (*aSemaphore)->whoseNext = kNobodyWaiting; // default value == nobody next in line
- (*aSemaphore)->nextSpot = 0;
- (*aSemaphore)->numberWaiting = 0;
-
- (*aSemaphore)->waitList = (ThreadID **) NewPtr ( sizeof(ThreadID *));
-
- if((*aSemaphore)->waitList == NULL)
- {
- ThreadEndCritical();
- return MemError();
- }
- else
- {
- *((*aSemaphore)->waitList) = (ThreadID *) NewPtr (sizeof(ThreadID) * maxInQueue);
- if( *((*aSemaphore)->waitList) == NULL)
- {
- ThreadEndCritical();
- return MemError();
- }
-
- /* Initialize all spots in the array to kNoThreadID, so we if somehow we think someone's waiting, this will
- * will catch us if there's not...
- */
- for(index = 0; index < maxInQueue; index++)
- (*((*aSemaphore)->waitList))[index] = kNoThreadID;
- }
-
- (*aSemaphore)->maxInQueue = maxInQueue;
-
- ThreadEndCritical();
-
- return noErr;
- }
-
- /*
- * GetArraySemaphore
- *
- * INPUT: ArraySemaphorePtr
- * RETURN: OSErr
- * WHEN CAN BE CALLED: From cooperative and preemptive threads
- *
- * This function attempts to get the semaphore. If it is available, we mark it unavailable and return.
- * If it is not and there are spaces left in the waiting list, then queue ourselves up and put ourself
- * to sleep. If there is no space in the queue then we "sit & spin", yielding to everyone until a spot
- * opens in the queue for us to place ourselves, or grab the semaphore.... Return an error if pass in
- * a NULL Ptr, else noErr.
- */
-
- pascal OSErr GetArraySemaphore( ArraySemaphorePtr aSemaphore )
- {
- ThreadID whoAmI;
-
- if(aSemaphore == NULL)
- return semaphoreButtHeadErr;
-
- ThreadBeginCritical();
-
- while(aSemaphore->numberWaiting == aSemaphore->maxInQueue)
- {
- // If all the spots in the queue are filled up, then End the critical section and Yield
- ThreadEndCritical();
- YieldToAnyThread();
-
- // After we return from the yield we must start up the critical section again
- ThreadBeginCritical();
- }
-
- if(GetCurrentThread(&whoAmI) != noErr)
- {
- DebugStr("\pFailed getting current Thread ID for whoAmI");
- }
-
- if( TestAndSetByte(&(aSemaphore->available)) )
- {
- if(GetCurrentThread(&(aSemaphore->theHolder)) != noErr)
- {
- DebugStr("\pFailed getting current Thread ID for the holder");
- }
- ThreadEndCritical();
- }
- else if(whoAmI != aSemaphore->theHolder)
- {
- (*(aSemaphore->waitList))[aSemaphore->nextSpot] = whoAmI;
-
- if(aSemaphore->whoseNext == kNobodyWaiting)
- aSemaphore->whoseNext = aSemaphore->nextSpot;
-
- if( (aSemaphore->nextSpot + 1) < aSemaphore->maxInQueue)
- aSemaphore->nextSpot++;
- else
- aSemaphore->nextSpot = 0;
-
- aSemaphore->numberWaiting++;
-
- SetThreadStateEndCritical(kCurrentThreadID, kStoppedThreadState, kNoThreadID);
- }
-
- return noErr;
- }
-
- /*
- * ReleaseArraySemaphore
- *
- * INPUT: ArraySemaphorePtr
- * RETURN: OSErr
- * WHEN CAN BE CALLED: From cooperative and preemptive threads
- *
- * This funtion releases the semaphore only if Threads are waiting for it. If this is the case, then the next
- * Thread in the waiting List is marked as the holder, woken up and current Thread is set to the Ready State,
- * telling the Thread Manager to now make the holder of the semaphore the current running Thread. Return an
- * error if any problems occur.
- */
-
- pascal OSErr ReleaseArraySemaphore( ArraySemaphorePtr aSemaphore)
- {
- if(aSemaphore == NULL)
- return semaphoreButtHeadErr;
-
- if(aSemaphore->whoseNext != kNobodyWaiting)
- {
- ThreadBeginCritical();
-
- aSemaphore->theHolder = (*(aSemaphore->waitList))[aSemaphore->whoseNext];
- aSemaphore->numberWaiting--;
- if(aSemaphore->numberWaiting != 0)
- {
- aSemaphore->whoseNext++;
- if(aSemaphore->whoseNext >= aSemaphore->maxInQueue)
- aSemaphore->whoseNext = 0;
-
- if(aSemaphore->whoseNext == aSemaphore->nextSpot)
- aSemaphore->whoseNext = kNobodyWaiting;
-
- }
-
- SetThreadState(aSemaphore->theHolder, kReadyThreadState, kNoThreadID);
-
- SetThreadStateEndCritical(kCurrentThreadID, kReadyThreadState, aSemaphore->theHolder);
- } else
- aSemaphore->available = kIsAvailable;
-
- return noErr;
- }
-
- /*
- * DeleteArraySemaphore
- *
- * INPUT: ArraySemaphorePtr
- * RETURN: OSErr
- * WHEN CAN BE CALLED: Only from a cooperative thread
- *
- * This function frees the memory used to allocate the array of waiting Threads. It returns the MemError
- * funtion, which will return an error if we recieved one with the DisposePtr call. If the semaphore == NULL
- * then return a semaphoreButtHeadErr. If there are threads waiting on the semaphore, return semaphoreWaitErr.
- */
-
- pascal OSErr DeleteArraySemaphore( ArraySemaphorePtr aSemaphore )
- {
- OSErr theError;
-
- ThreadBeginCritical();
-
- if(aSemaphore != NULL)
- {
- if(aSemaphore->numberWaiting != 0)
- {
- ThreadEndCritical();
- return semaphoreWaitErr;
- }
-
- DisposePtr( (Ptr) (*(aSemaphore->waitList)) );
- theError = MemError();
-
- if(theError == noErr)
- {
- DisposePtr( (Ptr) (aSemaphore->waitList) );
- theError = MemError();
- if(theError == noErr)
- {
- DisposePtr( (Ptr) aSemaphore );
- theError = MemError();
- }
- }
-
- ThreadEndCritical();
- return theError;
- }
-
- ThreadEndCritical();
-
- return semaphoreButtHeadErr;
- }
-